package fr.nts.framework.front.form.manager;

import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.util.List;

import net.sf.ehcache.hibernate.management.impl.BeanUtils;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

import com.google.common.collect.Lists;

import fr.nts.framework.core.commun.tool.ApplicationContextHelper;
import fr.nts.framework.front.form.AbstractForm;
import fr.nts.framework.front.form.annotation.Form;
import fr.nts.framework.front.form.annotation.FormField;
import fr.nts.framework.front.form.exception.NotValidFormException;
import fr.nts.framework.front.form.validation.FieldValidationResult;
import fr.nts.framework.front.form.validation.FormValidationResult;
import fr.nts.framework.front.form.validation.annotation.Constraint;
import fr.nts.framework.front.form.validation.validator.Validator;

/**
 * Le gestionnaire des formulaires NTS
 * 
 * @author sco
 * @version 1.0.0
 */
@Component
public class FormManager{

    protected static final Logger log = LoggerFactory.getLogger(FormManager.class);
    
    /**
     * Indique si le formulaire en entré est valide pour l'api NTS
     * 
     * @param form Le formulaire à tester.
     */
    public boolean isAValidForm(final AbstractForm form){
    	// Un formulaire est valide si il possède la bonne annotation: NTSForm
    	return (form != null && form.getClass().isAnnotationPresent(Form.class));
    }
    
    
    /**
     * Permet de récupérer les infos du formulaire en paramètre
     * 
     * @param form Le formulaire dont on veux les infos
     * @return Récupère les infos.
     */
    public Form getFormInfos(final AbstractForm form){
    	return form == null? null: form.getClass().getAnnotation(Form.class);
    }
    
    
    /**
     * Permet de récupérer les infos du champ du formulaire en paramètre
     * 
     * @param form Le champ dont on veux les infos
     * @return Récupère les infos.
     */
    public FormField getFieldInfos(final Field field){
    	return field.getAnnotation(FormField.class);
    }
    
    
    /**
     * Retourne la liste des champs formulaire NTS d'un objet en paramètre
     * 
     * @param form le formulaire à tester
     */
    public List<Field> getNTSFieldList(final AbstractForm form){
    	List<Field> fields = Lists.newArrayList();
    	
    	// Si le formulaire est valide
    	if(isAValidForm(form)){
    		Field[] formFields = form.getClass().getDeclaredFields();
			for(Field formField: formFields){
				
				// On ne récupère que les champs du formulaire qui ont pour annotation NTSField
				if((String.class.equals(formField.getType()) || String[].class.equals(formField.getType())) && formField.isAnnotationPresent(FormField.class)){
					fields.add(formField);
				}else{
					log.debug("Le champ [{}] n'est pas valide pour être gérer par l'API NTS Form.", formField);
				}
			}
    	}
    	
    	return fields;
    }
    
    
    /**
     * Valide le formulaire en paramètre
     * 
     * @param form Le formulaire à valider
     */
	public void validate(final AbstractForm form){
		
		List<Field> fields = getNTSFieldList(form);
		
		// Formulaire non valide: aucune données!
		if(fields.size() == 0){
			throw new NotValidFormException();
		}
		
		// Prépation du résultat de la validation
		FormValidationResult formValidationResult = new FormValidationResult(form);
		
		// On parcours tous les champs NTS
		for(Field field: fields){
			
			// Le champ NTS en cours
			//FormField ntsField = field.getAnnotation(FormField.class);
			
			for(Annotation annotation: field.getAnnotations()){
				
				// On récupère les annotations du champ NTS qui hérite de NTSConstraint
				if(annotation.annotationType().isAnnotationPresent(Constraint.class)){
					
					// La contrainte qui détermine le validateur
					Constraint ntsConstraint = annotation.annotationType().getAnnotation(Constraint.class);
					
					String beanName = ntsConstraint.validator();
					
					// On récupère le validateur grace au NTSConstraint
					Validator<Annotation> validator = getValidator(beanName);
					
					// La valeur du champ annoté
					String[] formValue = getFieldValue(form, 
							                           field);
					
					// On test le champ en question avec sa valeur
					FieldValidationResult result = validator.valid(formValue, 
							                                       annotation);
					
					// Si le test en échec, on l'ajoute à la liste du résultat final
					if(!result.isValid()){
						formValidationResult.getErrorFields().put(field.getName(), 
								                                  result);
					}
				}
			}
		}
		
		// Résultat de la validation
		form.setValidationResult(formValidationResult);
	}
	
	
	/**
	 * Récupère la valeur d'un champ du formulaire
	 */
	public String[] getFieldValue(Object form, 
			                      Field field){
		try{
			Object value = BeanUtils.getBeanProperty(form, 
					                                 field.getName());
			
			if(value == null){
				return new String[]{StringUtils.EMPTY};
			}else{
				if(value instanceof String){
					return new String[]{(String)value};
				}else if(value instanceof String[]){
					return (String[])value;
				}else{
					log.warn("La valeur {} n'est pas valide.", value);
				}
			}
		}catch(Exception e){
			log.warn("Erreur: ", e);
		}	
		
		return new String[]{StringUtils.EMPTY};
	}
	
	
	/**
	 * @return Retourne le validateur qui a pour nom le paramètre
	 */
	@SuppressWarnings("unchecked")
	private Validator<Annotation> getValidator(final String beanName){
		return (Validator<Annotation>)ApplicationContextHelper.getBean(beanName);
	}
}